package org.jflame.auth.authz;

import org.jflame.commons.util.CollectionHelper;
import org.jflame.commons.util.StringHelper;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * 通配符字符串权限表示类,示例:
 * <ul>
 * <li>user</li>
 * <li>user:*</li>
 * <li>user:view,edit</li>
 * <li>user:*:*</li>
 * </ul>
 * <p>
 * 摘自:{@link <a href="https://shiro.apache.org">shiro</a>}
 * 
 * @author charles.zhang
 */
public class WildcardPermit extends Permit implements Serializable {

    private static final long serialVersionUID = 1L;
    protected static final String WILDCARD_TOKEN = "*";
    protected static final String PART_DIVIDER_TOKEN = ":";
    protected static final String SUBPART_DIVIDER_TOKEN = ",";
    protected static final boolean DEFAULT_CASE_SENSITIVE = false;

    private List<Set<String>> parts;

    protected WildcardPermit() {
    }

    public WildcardPermit(String wildcardString) {
        this(wildcardString, DEFAULT_CASE_SENSITIVE);
    }

    public WildcardPermit(String wildcardString, boolean caseSensitive) {
        setParts(wildcardString, caseSensitive);
    }

    public boolean implies(Permit p) {
        if (!(p instanceof WildcardPermit)) {
            return false;
        }

        WildcardPermit wp = (WildcardPermit) p;
        List<Set<String>> otherParts = wp.getParts();

        int i = 0;
        for (Set<String> otherPart : otherParts) {
            // If this permission has less parts than the other permission, everything after the number of parts
            // contained
            // in this permission is automatically implied, so return true
            if (getParts().size() - 1 < i) {
                return true;
            } else {
                Set<String> part = getParts().get(i);
                if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
                    return false;
                }
                i++;
            }
        }

        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
        for (; i < getParts().size(); i++) {
            Set<String> part = getParts().get(i);
            if (!part.contains(WILDCARD_TOKEN)) {
                return false;
            }
        }

        return true;
    }

    protected void setParts(String wildcardString) {
        setParts(wildcardString, DEFAULT_CASE_SENSITIVE);
    }

    protected void setParts(String wildcardString, boolean caseSensitive) {
        wildcardString = StringHelper.trimToNull(wildcardString);
        if (StringHelper.isEmpty(wildcardString)) {
            throw new IllegalArgumentException(
                    "Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
        }

        if (!caseSensitive) {
            wildcardString = wildcardString.toLowerCase();
        }

        List<String> _parts = CollectionHelper.newList(wildcardString.split(PART_DIVIDER_TOKEN));

        this.parts = new ArrayList<Set<String>>();
        for (String part : _parts) {
            Set<String> subparts = CollectionHelper.newLinkedHashSet(part.split(SUBPART_DIVIDER_TOKEN));

            if (subparts.isEmpty()) {
                throw new IllegalArgumentException(
                        "Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
            }
            this.parts.add(subparts);
        }

        if (this.parts.isEmpty()) {
            throw new IllegalArgumentException(
                    "Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
        }
    }

    protected List<Set<String>> getParts() {
        return this.parts;
    }

    protected void setParts(List<Set<String>> parts) {
        this.parts = parts;
    }

    public String toString() {
        StringBuilder buffer = new StringBuilder();
        for (Set<String> part : parts) {
            if (buffer.length() > 0) {
                buffer.append(PART_DIVIDER_TOKEN);
            }
            Iterator<String> partIt = part.iterator();
            while (partIt.hasNext()) {
                buffer.append(partIt.next());
                if (partIt.hasNext()) {
                    buffer.append(SUBPART_DIVIDER_TOKEN);
                }
            }
        }
        return buffer.toString();
    }

    public boolean equals(Object o) {
        if (o instanceof WildcardPermit) {
            WildcardPermit wp = (WildcardPermit) o;
            return parts.equals(wp.parts);
        }
        return false;
    }

    public int hashCode() {
        return parts.hashCode();
    }

}
